# -*- coding: utf-8 -*-
# @version : 1.0
# @File    : redis.py
# @Date    : 2024/9/13 11:14
# @Author  : GlenZhao
# @Desc    : redis连接
from typing import Dict
from fastapi import FastAPI
from redis import asyncio as aioredis
from redis.client import PubSub
from redis.exceptions import ConnectionError, TimeoutError

from application.settings import REDIS_DB_URL
from utils.tools import SingletonDecorator


class Redis:
    """
   aioredis.from_url(url, *, encoding=None, parser=None, decode_responses=False, db=None, password=None, ssl=None,
   connection_cls=None, loop=None, **kwargs) 方法是 aioredis 库中用于从 Redis 连接 URL 创建 Redis 连接对象的方法。

   以下是该方法的参数说明：
   url：Redis 连接 URL。例如 redis://localhost:6379/0。
   encoding：可选参数，Redis 编码格式。默认为 utf-8。
   parser：可选参数，Redis 数据解析器。默认为 None，表示使用默认解析器。
   decode_responses：可选参数，是否将 Redis 响应解码为 Python 字符串。默认为 False。
   db：可选参数，Redis 数据库编号。默认为 None。
   password：可选参数，Redis 认证密码。默认为 None，表示无需认证。
   ssl：可选参数，是否使用 SSL/TLS 加密连接。默认为 None。
   connection_cls：可选参数，Redis 连接类。默认为 None，表示使用默认连接类。
   loop：可选参数，用于创建连接对象的事件循环。默认为 None，表示使用默认事件循环。
   **kwargs：可选参数，其他连接参数，用于传递给 Redis 连接类的构造函数。

   aioredis.from_url() 方法的主要作用是将 Redis 连接 URL 转换为 Redis 连接对象。
   除了 URL 参数外，其他参数用于指定 Redis 连接的各种选项，例如 Redis 数据库编号、密码、SSL/TLS 加密等等。可以根据需要选择使用这些选项。

   health_check_interval 是 aioredis.from_url() 方法中的一个可选参数，用于设置 Redis 连接的健康检查间隔时间。
   健康检查是指在 Redis 连接池中使用的连接对象会定期向 Redis 服务器发送 PING 命令来检查连接是否仍然有效。
   该参数的默认值是 0，表示不进行健康检查。如果需要启用健康检查，则可以将该参数设置为一个正整数，表示检查间隔的秒数。
   例如，如果需要每隔 5 秒对 Redis 连接进行一次健康检查，则可以将 health_check_interval 设置为 5
   :param app: fastapi
   :param status:
   :return: 无
    """
    def __init__(self, url=REDIS_DB_URL, decode_responses=True, health_check_interval=5):
        self.url = url
        self.decode_responses = decode_responses
        self.health_check_interval = health_check_interval
        self._pool = None
        self.client: aioredis.Redis | None = None
        self.all_pubsub: Dict[str, PubSub] = {}

    def __call__(self, *args, **kwargs):
        return self

    async def _create_pool(self):
        self._pool = aioredis.ConnectionPool.from_url(
            self.url,
            decode_responses=self.decode_responses,
            health_check_interval=self.health_check_interval
        )

    @staticmethod
    def _gen_key(key: str) -> str:
        return "fz-" + key

    async def connect(self):
        if not self._pool:
            await self._create_pool()
        if not self.client:
            self.client = aioredis.Redis(connection_pool=self._pool)
        response = await self.client.ping()
        if response:
            print("Redis 连接成功")
        else:
            print("Redis 连接失败")

    async def close(self):
        if self._pool:
            await self._pool.disconnect()
            self.client = None
            self._pool = None

    async def execute(self, command, *args, **kwargs):
        while True:
            try:
                if self.client is None:
                    await self.close()
                    await self.connect()
                else:
                    cmd = getattr(self.client, command)
                    result = await cmd(*args, **kwargs)
                    return result
            except (ConnectionError, TimeoutError) as e:
                # 如果发生连接错误或超时错误，则尝试重新连接
                print(f"Connection error: {e}. Attempting to reconnect...")
                await self.close()
                await self.connect()
            except Exception as e:
                print(f"An unexpected error occurred: {e}")
                raise

    async def set(self, key, value, **kwargs):
        res = await self.execute('set', self._gen_key(key), value, **kwargs)
        return res

    async def get(self, key):
        return await self.execute('get', self._gen_key(key))

    async def exists(self, key) -> bool:
        res = await self.execute('exists', self._gen_key(key))
        return res

    async def delete(self, key):
        return await self.execute('delete', self._gen_key(key))
    
    async def eval(self, script, key, value):
        return await self.execute('eval', script, 1, self._gen_key(key), value)

    async def publish(self, channel, data):
        await self.execute('publish', channel, data)

    async def subscribe(self, channel):
        while True:
            try:
                if self.client is None:
                    await self.close()
                    await self.connect()
                elif channel not in self.all_pubsub:
                    ps = self.client.pubsub()
                    await ps.subscribe(channel)
                    self.all_pubsub[channel] = ps
                    break
                else:
                    break
            except Exception as e:
                print(f"An unexpected error occurred: {e}")

    async def unsubscribe(self, channel):
        if channel in self.all_pubsub:
            await self.all_pubsub[channel].unsubscribe(channel)
            del self.all_pubsub[channel]

    def get_pubsub(self, channel):
        if channel in self.all_pubsub:
            return self.all_pubsub[channel]
        return None

    async def listen(self, channel):
        if channel in self.all_pubsub:
            while True:
                try:
                    message = await self.all_pubsub[channel].get_message(ignore_subscribe_messages=True)
                    if message:
                        yield message
                except (ConnectionError, TimeoutError) as e:
                    print(f"Connection error: {e}. Attempting to reconnect...")
                    await self.close()
                    await self.connect()
                    await self.subscribe(channel)  # 重新订阅之前的频道
                except Exception as e:
                    print(f"An unexpected error occurred: {e}")
                    raise


@SingletonDecorator
class RedisManager(Redis):
    pass



async def connect_redis(app: FastAPI, status: bool):
    """
   把 redis 挂载到 app 对象上面
   """ 

    if status:
        redis_manager = RedisManager()
        await redis_manager.connect()
        app.state.redis_manager = redis_manager
        # FastAPICache.init(RedisBackend(aioredis.from_url(REDIS_DB_URL, decode_responses=True, health_check_interval=5)), prefix="fz-cache")
    else:
        print("Redis 连接关闭")
        await  app.state.redis_manager.close()